#include <slothttpd/parser/headers_parser.hpp>
#include <slothttpd/logger.hpp>

#include <algorithm>

#include <boost/algorithm/string/trim.hpp>

namespace slothttpd {
namespace parser {

const char LF = '\n';
const char CR = '\r';

headers_parser::headers_parser() :
    name_start(nullptr),
    name_end(nullptr),
    value_start(nullptr),
    value_end(nullptr),
    state_(state_name_first_char)
{
}

result_type headers_parser::parse(receive_buffer_list &list, 
                                  boost::function<void(char_range_t, char_range_t)> callback)
{
    char_range_t r = list.get_data();
    auto start_size = r.size();
    
    if (r.empty())
        return parse_again;
    
    while (!r.empty()) {
        const char current = r.front();
        
        switch (state_) {
        case state_name_first_char:
            if (std::isprint(current)) {
                name_start  = r.begin();
                r.advance_begin(1);
                
                state_ = state_name_char;
            } else if (current == CR) {
                r.advance_begin(1);
                
                state_ = state_cr_lf_2;
            } else if (current == LF) {
                state_ = state_lf_2_end;
            } else {
                state_ = state_error;
            }
            break;
            
        case state_name_char:
            if (current == ':') {               // semi-colon after header name
                name_end = r.begin();
                r.advance_begin(1);
                
                state_ = state_value_spaces;
            } else if (std::isspace(current)) {
                state_ = state_error;
            } else if (std::isprint(current)) { // header name
                r.advance_begin(1);
            } else { //unprintable garbage
                state_ = state_error;
            }
            break;
            
        case state_value_spaces:
            if (current == ' ') {               // spaces before header value
                r.advance_begin(1);
            } else if (std::isprint(current)){  // first char of header value
                value_start = r.begin();
                
                state_ = state_value_char;
            } else if (current == CR || current == LF) {
                value_end = value_start;
                if (current == CR)
                    r.advance_begin(1);
                
                state_ = state_lf_1;
            } else {
                state_ = state_error;
            }
            break;
            
        case state_value_char:
            if (current == LF || current == CR) { //   1st LF, next should be LF or CR+LF or CR
                value_end = r.begin();
                
                // why this throws ? trim manualy
                // value = boost::algorithm::trim_right_copy(value);
                
                char *p = value_end;
                while (p >= value_start && std::isspace(*p)) {
                    p--;
                }
                
                char_range_t name(name_start, name_end);
                char_range_t value(value_start, p + 1);
                
                // TODO: save header [(name_start, name_end), (value_start, value_end)]
                callback(name, value);
                
                if (current == LF) {
                    state_ = state_lf_1;
                } else { // CR
                    r.advance_begin(1); // take CR, next must be LF
                    
                    state_ = state_lf_1;
                }
            } else {                            // allow anything else
                r.advance_begin(1);
            }
            break;
            
        case state_lf_1:
            if (current == LF) {
                r.advance_begin(1); // take LF
                
                state_ = state_cr_lf_2;
            } else {
                state_ = state_error;
            }
            break;
            
        case state_cr_lf_2:
            if (current == CR) { // should be CR of CR+LF (second line separator), wait for LF
                r.advance_begin(1);
                
                state_ = state_lf_2_end;
            } else if (current == LF) { // second line separator, just switch to X_LF_2
                state_ = state_lf_2_end;
            } else {
                state_ = state_name_first_char;
            }
            break;
            
        case state_lf_2_end:
            if (current == LF) {
                r.advance_begin(1);
                list.consume(start_size - r.size());
                
                return parse_ok;
            } else {
                state_ = state_error;
            }
            break;
        
        case state_error:
            logger() << "parse_headers: ERROR\n";
            list.consume(start_size - r.size());
            
            return parse_error;
        }
    }
    
    list.consume(start_size - r.size());
    
    return parse(list, callback);
}

void headers_parser::reallocate_part_of_word(data_buffer &old, data_buffer &new_, char **ptr) noexcept
{
    char *old_end = old.unprocessed().end();
    std::size_t part_name_size = old_end - *ptr;
    
    // copy beginning of header name to new buffer
    std::copy(*ptr, old_end, new_.data);
    new_.unprocessed_first = new_.unprocessed_last = new_.data;
    
    new_.unprocessed_first += part_name_size;
    new_.unprocessed_last  += part_name_size;
    
    // remove moved data from old buffer. now old buffer contains data 
    // from (old.data, old.data + old.unprocessed_first)
    old.unprocessed_first  = *ptr;
    old.unprocessed_last   = *ptr;
    
    *ptr = new_.data;
}

void headers_parser::reallocate_buffer(data_buffer &old, data_buffer &new_) noexcept
{
    switch (state_) {
    case state_name_first_char:
    case state_name_char:
        // 'name_start' initialized with beginning of header name in old buffer
        // 'name_end' not initialized
        // copy beginning of header name to new buffer
        reallocate_part_of_word(old, new_, &name_start);
        break;
        
    case state_value_char:
        reallocate_part_of_word(old, new_, &value_start);
        break;
        
    default:
        break;
    }
}

} //namespace parser
} //namespace slothttpd
